console.log("3D百练 2024.08.19");

import * as d3 from "d3";
import { Tween, Easing } from "@tweenjs/tween.js";
import {
  AmbientLight,
  AxesHelper,
  Color,
  CubeTextureLoader,
  DirectionalLight,
  DirectionalLightHelper,
  GridHelper,
  Group,
  LinearToneMapping,
  MeshStandardMaterial,
  PerspectiveCamera,
  Raycaster,
  Scene,
  Vector2,
  Vector3,
  WebGLRenderer,
} from "three";
import {
  EffectComposer,
  GLTFLoader,
  OrbitControls,
  OutlinePass,
  RenderPass,
  FXAAShader,
  ShaderPass,
  GammaCorrectionShader,
  CSS3DObject,
  CSS3DRenderer,
} from "three/examples/jsm/Addons.js";

export default class Y3DEngine {
  constructor(options) {
    this.options = options;
    this.init();
    this.animate();
  }

  init() {
    this.initScene();
    this.initMaterials();
    this.initCamera();
    this.initLight();
    this.initRenderer();
    this.initControls();
    this.initModel();
    this.initHelper();
    this.initEvent();
    this.initComposer();
    this.initTween();
  }

  initTween() {
    this.tween = null;
  }

  initScene() {
    this.scene = new Scene();
  }

  initMaterials() {
    this.materials = {};
    this.materials["上部结构,上部一般构件,横隔板"] = new MeshStandardMaterial({
      color: 0x808080,
      roughness: 0.5,
      metalness: 0.1,
    });
    this.materials["上部结构,上部一般构件,湿接缝"] = new MeshStandardMaterial({
      color: 0x909090,
      roughness: 0.5,
      metalness: 0.1,
    });
    this.materials["上部结构,上部承重构件,T梁"] = new MeshStandardMaterial({
      color: 0x999999,
      roughness: 0.6,
      metalness: 0.05,
    });
  }

  initCamera() {
    this.camera = new PerspectiveCamera(
      45,
      window.innerWidth / window.innerHeight,
      0.1,
      100000
    );
    this.camera.position.z = 3;
  }

  initLight() {
    this.lights = new Group();
    this.scene.add(this.lights);
    const light = new AmbientLight(0xffffff, 0.3);
    this.lights.add(light);

    const light2 = new DirectionalLight(0xffffff, 0.7);
    light2.position.set(0, 1, 1);
    this.lights.add(light2);
    this.scene.add(new DirectionalLightHelper(light2, 1));

    const light3 = new DirectionalLight(0xffffff, 0.5);
    light3.position.set(0, 1, -1);
    this.lights.add(light3);
    this.scene.add(new DirectionalLightHelper(light3, 1));

    const light4 = new DirectionalLight("gray", 0.5);
    light4.position.set(0, -1, -1);
    this.lights.add(light4);
    this.scene.add(new DirectionalLightHelper(light4, 1));

    const url = "px180.png";
    const cubeTexture = new CubeTextureLoader()
      .setPath("../assets/")
      .load([url, url, url, url, url, url]);
    this.scene.environment = cubeTexture;
    this.scene.environmentIntensity = 0.2;

    this.cameraLight = new DirectionalLight(0xffffff, 0.3);
    this.cameraLight.position.set(0, 1, -1);
    this.lights.add(this.cameraLight);
  }
  initRenderer() {
    this.renderer = new WebGLRenderer({ antialias: true });
    this.renderer.setSize(window.innerWidth, window.innerHeight);
    this.renderer.setPixelRatio(window.devicePixelRatio);
    this.renderer.setClearColor(0x312c42);
    // this.renderer.toneMapping = LinearToneMapping;
    // this.renderer.toneMappingExposure = 1.0; // 适当增加曝光值以提亮场景
    document.body.appendChild(this.renderer.domElement);

    // 创建 CSS3DRenderer

    this.css3renderer = new CSS3DRenderer();
    this.css3renderer.setSize(window.innerWidth, window.innerHeight);
    this.css3renderer.domElement.style.position = "absolute";
    this.css3renderer.domElement.style.top = "0px";
    //设置.pointerEvents=none，解决HTML元素标签对threejs canvas画布鼠标事件的遮挡
    this.css3renderer.domElement.style.pointerEvents = "none";
    document.body.appendChild(this.css3renderer.domElement);
  }

  initComposer() {
    // `.getPixelRatio()`获取`renderer.setPixelRatio()`设置的值
    const pixelRatio = this.renderer.getPixelRatio(); //获取设备像素比
    // width、height是canva画布的宽高度
    const fxaaPass = new ShaderPass(FXAAShader);
    this.fxaaPass = fxaaPass;

    // fxaaPass.material.uniforms["resolution"].value.x =
    //   1 / (window.innerWidth * pixelRatio);
    // fxaaPass.material.uniforms["resolution"].value.y =
    //   1 / (window.innerHeight * pixelRatio);

    this.composer = new EffectComposer(this.renderer);
    this.renderPass = new RenderPass(this.scene, this.camera);
    this.outlinePass = new OutlinePass(new Vector2(), this.scene, this.camera);

    this.composer.addPass(this.renderPass);
    this.outlinePass.edgeStrength = 2;
    this.outlinePass.visibleEdgeColor = new Color(0x00ff00);
    this.outlinePass.hiddenEdgeColor = new Color(0x00aa00);
    const gammaCorrectionShader = new ShaderPass(GammaCorrectionShader);
    this.composer.addPass(gammaCorrectionShader);
    this.composer.addPass(this.outlinePass);
    this.composer.addPass(fxaaPass);
  }

  initControls() {
    this.controls = new OrbitControls(this.camera, this.renderer.domElement);
  }

  initCSS3Object() {
    var div = document.createElement("div");
    div.innerHTML = "";
    div.style.width = "100px";
    div.style.height = "100px";
    div.style.position = "absolute";
    this.scene.add(new CSS3DObject(div));

    const curveData = d3
      .line()
      .x((d) => d.x)
      .y((d) => d.y)
      .curve(d3.curveBasis)([
      { x: 0, y: 0 },
      { x: 1, y: 1 },
      { x: 2, y: 2 },
      { x: 3, y: 1 },
      { x: 4, y: 0 },
    ]);

    const svg = d3
      .select(div)
      .append("svg")
      .attr("width", 500)
      .attr("height", 500);

    const path = svg
      .append("path")
      .attr("d", curveData)
      .attr("stroke", "black")
      .attr("fill", "none");
  }

  initModel() {
    this.initCSS3Object();
    const loader = new GLTFLoader();
    let url = "../assets/芙蓉镇大桥20240614.glb";
    url = "../assets/茅草街大桥2.glb";
    url = "../assets/峒河高架桥（上行）.glb";
    const factor = 1;
    const distance = 50;
    loader.load(url, (gltf) => {
      gltf.scene.children.forEach((child) => {
        if (child.isMesh && child.name) {
          const name = child.name.split("$")[0];
          if (this.materials.hasOwnProperty(name)) {
            child.material = this.materials[name];
          }
        }
      });
      gltf.scene.scale.set(factor, factor, factor);
      gltf.scene.updateMatrixWorld();
      this.scene.add(gltf.scene);
      const mesh =
        gltf.scene.children[Math.floor(gltf.scene.children.length / 2)];
      const { matrixWorld } = gltf.scene;
      const position = mesh.position.clone().applyMatrix4(matrixWorld);

      this.camera.position.copy(
        new Vector3(0, distance, distance).add(position)
      );
      this.controls.target.copy(position);
      console.log(
        position,
        JSON.stringify(matrixWorld),
        matrixWorld,
        mesh.position
      );
      // this.controls.target.copy(position);
    });
  }

  initHelper() {
    this.helpers = new Group();
    this.scene.add(this.helpers);
    this.axesHelper = new AxesHelper(10);
    this.helpers.add(this.axesHelper);
    this.helpers.add(new GridHelper(10, 20));
  }

  initEvent() {
    window.addEventListener("resize", () => {
      this.onWindowResize();
    });
    window.addEventListener("pointerup", this.onPointUp);
  }

  onWindowResize = () => {
    this.camera.aspect = window.innerWidth / window.innerHeight;
    this.camera.updateProjectionMatrix();
    this.renderer.setSize(window.innerWidth, window.innerHeight);
    this.css3renderer.setSize(window.innerWidth, window.innerHeight);
    this.composer.setSize(window.innerWidth, window.innerHeight);

    const { fxaaPass } = this;
    const pixelRatio = this.renderer.getPixelRatio();
    fxaaPass.material.uniforms["resolution"].value.x =
      1 / (window.innerWidth * pixelRatio);
    fxaaPass.material.uniforms["resolution"].value.y =
      1 / (window.innerHeight * pixelRatio);
  };

  animate = (time) => {
    this.tween?.update(time);
    requestAnimationFrame(this.animate);
    // this.renderer.render(this.scene, this.camera);
    this.cameraLight.position.copy(
      this.camera.position.clone().sub(this.controls.target)
    );
    this.composer.render();
    this.css3renderer.render(this.scene, this.camera);
    this.controls.update();
  };

  onPointUp = (e) => {
    if (e.ctrlKey) {
      console.log(e);
      const raycaster = new Raycaster();
      const mouse = new Vector2();
      mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
      mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
      raycaster.setFromCamera(mouse, this.camera);
      const intersects = raycaster.intersectObjects(this.scene.children);
      if (intersects.length > 0) {
        const intersect = intersects[0];
        console.log(intersect);
        this.outlinePass.selectedObjects = [intersect.object];
        this.highlight(intersect.object);
        this.seePoint(intersect.point);
      }
    }
  };

  seePoint = (dest) => {
    const {
      camera: { position },
      controls: { target },
      axesHelper,
    } = this;
    const a = position.clone().sub(target);
    a.clampLength(0, 300).add(dest);

    this.tween = new Tween({ position, target })
      .to(
        {
          position: a,
          target: dest,
        },
        400
      )
      .easing(Easing.Quadratic.InOut)
      .onUpdate((current, elapsed) => {
        axesHelper.position.copy(current.target);
      })
      .start();
    // this.controls.target.copy(dest);
    // this.camera.position.copy(dest).add(direction);
  };

  highlight = (obj) => {};
}

window.addEventListener("DOMContentLoaded", () => {
  const engine = new Y3DEngine();
  window.engine = engine;
});
